summaryrefslogtreecommitdiffstats
path: root/src/audio_core/sink/sink_stream.h
blob: 9806e6d986eabf7c6ac6f6515c6905cf956b9e3e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <array>
#include <atomic>
#include <chrono>
#include <memory>
#include <mutex>
#include <span>
#include <vector>

#include "audio_core/common/common.h"
#include "common/common_types.h"
#include "common/reader_writer_queue.h"
#include "common/ring_buffer.h"
#include "common/thread.h"

namespace Core {
class System;
} // namespace Core

namespace AudioCore::Sink {

enum class StreamType {
    Render,
    Out,
    In,
};

struct SinkBuffer {
    u64 frames;
    u64 frames_played;
    u64 tag;
    bool consumed;
};

/**
 * Contains a real backend stream for outputting samples to hardware,
 * created only via a Sink (See Sink::AcquireSinkStream).
 *
 * Accepts a SinkBuffer and samples in PCM16 format to be output (see AppendBuffer).
 * Appended buffers act as a FIFO queue, and will be held until played.
 * You should regularly call IsBufferConsumed with the unique SinkBuffer tag to check if the buffer
 * has been consumed.
 *
 * Since these are a FIFO queue, IsBufferConsumed must be checked in the same order buffers were
 * appended, skipping a buffer will result in the queue getting stuck, and all following buffers to
 * never release.
 *
 * If the buffers appear to be stuck, you can stop and re-open an IAudioIn/IAudioOut service (this
 * is what games do), or call ClearQueue to flush all of the buffers without a full restart.
 */
class SinkStream {
public:
    explicit SinkStream(Core::System& system_, StreamType type_) : system{system_}, type{type_} {}
    virtual ~SinkStream() {
        Unstall();
    }

    /**
     * Finalize the sink stream.
     */
    virtual void Finalize() {}

    /**
     * Start the sink stream.
     *
     * @param resume - Set to true if this is resuming the stream a previously-active stream.
     *                 Default false.
     */
    virtual void Start(bool resume = false) {}

    /**
     * Stop the sink stream.
     */
    virtual void Stop() {}

    /**
     * Check if the stream is paused.
     *
     * @return True if paused, otherwise false.
     */
    bool IsPaused() const {
        return paused;
    }

    /**
     * Get the number of system channels in this stream.
     *
     * @return Number of system channels.
     */
    u32 GetSystemChannels() const {
        return system_channels;
    }

    /**
     * Set the number of channels the system expects.
     *
     * @param channels - New number of system channels.
     */
    void SetSystemChannels(u32 channels) {
        system_channels = channels;
    }

    /**
     * Get the number of channels the hardware supports.
     *
     * @return Number of channels supported.
     */
    u32 GetDeviceChannels() const {
        return device_channels;
    }

    /**
     * Get the system volume.
     *
     * @return The current system volume.
     */
    f32 GetSystemVolume() const {
        return system_volume;
    }

    /**
     * Get the device volume.
     *
     * @return The current device volume.
     */
    f32 GetDeviceVolume() const {
        return device_volume;
    }

    /**
     * Set the system volume.
     *
     * @param volume_ - The new system volume.
     */
    void SetSystemVolume(f32 volume_) {
        system_volume = volume_;
    }

    /**
     * Set the device volume.
     *
     * @param volume_ - The new device volume.
     */
    void SetDeviceVolume(f32 volume_) {
        device_volume = volume_;
    }

    /**
     * Get the number of queued audio buffers.
     *
     * @return The number of queued buffers.
     */
    u32 GetQueueSize() const {
        return queued_buffers.load();
    }

    /**
     * Set the maximum buffer queue size.
     */
    void SetRingSize(u32 ring_size) {
        max_queue_size = ring_size;
    }

    /**
     * Append a new buffer and its samples to a waiting queue to play.
     *
     * @param buffer  - Audio buffer information to be queued.
     * @param samples - The s16 samples to be queue for playback.
     */
    virtual void AppendBuffer(SinkBuffer& buffer, std::vector<s16>& samples);

    /**
     * Release a buffer. Audio In only, will fill a buffer with recorded samples.
     *
     * @param num_samples - Maximum number of samples to receive.
     * @return Vector of recorded samples. May have fewer than num_samples.
     */
    virtual std::vector<s16> ReleaseBuffer(u64 num_samples);

    /**
     * Empty out the buffer queue.
     */
    void ClearQueue();

    /**
     * Callback for AudioIn.
     *
     * @param input_buffer - Input buffer to be filled with samples.
     * @param num_frames - Number of frames to be filled.
     */
    void ProcessAudioIn(std::span<const s16> input_buffer, std::size_t num_frames);

    /**
     * Callback for AudioOut and AudioRenderer.
     *
     * @param output_buffer - Output buffer to be filled with samples.
     * @param num_frames - Number of frames to be filled.
     */
    void ProcessAudioOutAndRender(std::span<s16> output_buffer, std::size_t num_frames);

    /**
     * Stall core processes if the audio thread falls too far behind.
     */
    void Stall();

    /**
     * Unstall core processes.
     */
    void Unstall();

    /**
     * Get the total number of samples expected to have been played by this stream.
     *
     * @return The number of samples.
     */
    u64 GetExpectedPlayedSampleCount();

    /**
     * Waits for free space in the sample ring buffer
     */
    void WaitFreeSpace();

protected:
    /// Core system
    Core::System& system;
    /// Type of this stream
    StreamType type;
    /// Set by the audio render/in/out system which uses this stream
    u32 system_channels{2};
    /// Channels supported by hardware
    u32 device_channels{2};
    /// Is this stream currently paused?
    std::atomic<bool> paused{true};
    /// Name of this stream
    std::string name{};

private:
    /// Ring buffer of the samples waiting to be played or consumed
    Common::RingBuffer<s16, 0x10000> samples_buffer;
    /// Audio buffers queued and waiting to play
    Common::ReaderWriterQueue<SinkBuffer> queue;
    /// The currently-playing audio buffer
    SinkBuffer playing_buffer{};
    /// The last played (or received) frame of audio, used when the callback underruns
    std::array<s16, MaxChannels> last_frame{};
    /// Number of buffers waiting to be played
    std::atomic<u32> queued_buffers{};
    /// The ring size for audio out buffers (usually 4, rarely 2 or 8)
    u32 max_queue_size{};
    /// Locks access to sample count tracking info
    std::mutex sample_count_lock;
    /// Minimum number of total samples that have been played since the last callback
    u64 min_played_sample_count{};
    /// Maximum number of total samples that can be played since the last callback
    u64 max_played_sample_count{};
    /// The time the two above tracking variables were last written to
    std::chrono::microseconds last_sample_count_update_time{};
    /// Set by the audio render/in/out system which uses this stream
    f32 system_volume{1.0f};
    /// Set via IAudioDevice service calls
    f32 device_volume{1.0f};
    /// Signalled when ring buffer entries are consumed
    std::condition_variable release_cv;
    std::mutex release_mutex;
    std::mutex stall_guard;
    std::unique_lock<std::mutex> stalled_lock;
};

using SinkStreamPtr = std::unique_ptr<SinkStream>;

} // namespace AudioCore::Sink